/*
 * Copyright (C) 2021 The Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package com.oushangfeng.pinneddemo.spinner;

import com.oushangfeng.pinneddemo.ResourceTable;

import ohos.aafwk.ability.Ability;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.components.ComponentParent;
import ohos.agp.components.ListContainer;
import ohos.agp.components.Text;
import ohos.agp.components.element.Element;
import ohos.agp.components.element.PixelMapElement;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.utils.Color;
import ohos.agp.utils.LayoutAlignment;
import ohos.agp.utils.Matrix;
import ohos.agp.utils.TextAlignment;
import ohos.agp.window.dialog.BaseDialog;
import ohos.agp.window.dialog.IDialog;
import ohos.agp.window.dialog.PopupDialog;
import ohos.app.AbilityContext;
import ohos.app.Context;
import ohos.global.resource.NotExistException;
import ohos.global.resource.ResourceManager;
import ohos.global.resource.WrongTypeException;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.multimodalinput.event.TouchEvent;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * MaterialSpinner
 *
 * @since 2021-03-27
 */
public class MaterialSpinner extends Text implements Component.TouchEventListener {
    static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0x00201, "MaterialSpinner");
    private static final int MAX_HEIGHT = 1080;
    private static final float DEFAULT_FACTOR = 0.8f;
    private static final int NUM_2 = 2;
    private static final int NUM_3 = 3;
    private static final int NUM_10 = 10;
    private static final int NUM_20 = 20;
    private static final int NUM_37 = 37;
    private static final int NUM_45 = 45;
    private static final int NUM_10000 = 10000;
    boolean isRtl;
    private boolean isShow = false;
    private OnNothingSelectedListener onNothingSelectedListener;
    private OnItemSelectedListener onItemSelectedListener;
    private MaterialSpinnerBaseAdapter adapter;
    private PopupDialog popupWindow;
    private ListContainer listView;
    private PixelMapElement arrowDrawable;
    private boolean isHideArrow;
    private boolean isNothingSelected;
    private int spinnerWidth;
    private int popupWindowMaxHeight;
    private int popupWindowHeight;
    private int selectedIndex;
    private int backgroundColor;
    private Element backgroundSelector;
    private int arrowColor;
    private int arrowColorDisabled;
    private int textColor;
    private int textSize;
    private int hintColor;
    private int popupPaddingTop;
    private int popupPaddingLeft;
    private int popupPaddingBottom;
    private int popupPaddingRight;
    private String hintText;
    private MaterialSpinnerBaseAdapter mAdapter;

    /**
     * MaterialSpinner
     *
     * @param context
     */
    public MaterialSpinner(Context context) {
        super(context);
    }

    /**
     * MaterialSpinner
     *
     * @param context
     * @param attrs
     */
    public MaterialSpinner(Context context, AttrSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    /**
     * MaterialSpinner
     *
     * @param context
     * @param attrs
     * @param styleName
     */
    public MaterialSpinner(Context context, AttrSet attrs, String styleName) {
        super(context, attrs, styleName);
        init(context, attrs);
    }

    private void init(Context context, AttrSet attrs) {
        HiLog.info(LABEL, "init() ");
        Color defaultColor = getTextColor();
        isRtl = Utils.isRtl(context);
        int paddingLeft = 0;
        int paddingTop = 0;
        int paddingRight = 0;
        int paddingBottom = 0;
        int defaultPaddingLeft;
        int defaultPaddingTop;
        int defaultPaddingRight;
        int defaultPaddingBottom;
        int defaultPopupPaddingLeft;
        int defaultPopupPaddingTop;
        int defaultPopupPaddingRight;
        int defaultPopupPaddingBottom;
        spinnerWidth = this.getWidth();
        ResourceManager resourceManager = getResourceManager();
        try {
            defaultPaddingLeft = (int) resourceManager.getElement(ResourceTable.Float_ms__padding_top).getFloat();
            defaultPaddingRight = (int) resourceManager.getElement(ResourceTable.Float_ms__padding_top).getFloat();
            defaultPaddingBottom = (int) resourceManager.getElement(ResourceTable.Float_ms__padding_top).getFloat();
            defaultPaddingTop = (int) resourceManager.getElement(ResourceTable.Float_ms__padding_top).getFloat();
            if (isRtl) {
                defaultPaddingRight = (int) resourceManager.getElement(ResourceTable.Float_ms__padding_left).getFloat();
            } else {
                defaultPaddingLeft = (int) resourceManager.getElement(ResourceTable.Float_ms__padding_left).getFloat();
            }
            defaultPopupPaddingLeft = (int) resourceManager.getElement(ResourceTable.Float_ms__padding_left).getFloat();
            defaultPopupPaddingRight = (int) resourceManager
                    .getElement(ResourceTable.Float_ms__padding_left).getFloat();
            defaultPopupPaddingTop = (int) resourceManager.getElement(ResourceTable.Float_ms__padding_top).getFloat();
            defaultPopupPaddingBottom = (int) resourceManager
                    .getElement(ResourceTable.Float_ms__padding_top).getFloat();

            backgroundColor = TypedAttrUtils.getIntColor(attrs, "ms_background_color", Color.WHITE.getValue());
            backgroundSelector = attrs.getAttr("ms_background_selector").get().getElement();
            HiLog.info(LABEL, "backgroundSelector = " + backgroundSelector);
            textColor = TypedAttrUtils.getIntColor(attrs, "ms_text_color", defaultColor.getValue());
            hintColor = TypedAttrUtils.getIntColor(attrs, "ms_hint_color", defaultColor.getValue());
            arrowColor = TypedAttrUtils.getIntColor(attrs, "ms_arrow_tint", textColor);
            isHideArrow = TypedAttrUtils.getBoolean(attrs, "ms_hide_arrow", false);
            hintText = TypedAttrUtils.getString(attrs, "ms_hint", "");

            popupWindowMaxHeight = TypedAttrUtils.getDimensionPixelSize(attrs, "ms_dropdown_max_height", MAX_HEIGHT);
            popupWindowHeight = TypedAttrUtils.getLayoutDimension(attrs, "ms_dropdown_height",
                    ComponentContainer.LayoutConfig.MATCH_CONTENT);
            HiLog.info(LABEL, "exception = " + "getLayoutDimension 2");
            paddingTop = TypedAttrUtils.getDimensionPixelSize(attrs, "ms_padding_top", defaultPaddingTop);
            paddingLeft = TypedAttrUtils.getDimensionPixelSize(attrs, "ms_padding_left", defaultPaddingLeft);
            paddingBottom = TypedAttrUtils.getDimensionPixelSize(attrs, "ms_padding_bottom", defaultPaddingBottom);
            paddingRight = TypedAttrUtils.getDimensionPixelSize(attrs, "ms_padding_right", defaultPaddingRight);
            popupPaddingTop = TypedAttrUtils.getDimensionPixelSize(
                    attrs, "ms_popup_padding_top", defaultPopupPaddingTop);
            popupPaddingLeft = TypedAttrUtils.getDimensionPixelSize(
                    attrs, "ms_popup_padding_left", defaultPopupPaddingLeft);
            popupPaddingBottom = TypedAttrUtils.getDimensionPixelSize(
                    attrs, "ms_popup_padding_bottom", defaultPopupPaddingBottom);
            popupPaddingRight = TypedAttrUtils.getDimensionPixelSize(
                    attrs, "ms_popup_padding_right", defaultPopupPaddingRight);
            arrowColorDisabled = Utils.lighter(arrowColor, DEFAULT_FACTOR);
            HiLog.info(LABEL, "arrowColorDisabled = " + arrowColorDisabled);

            HiLog.info(LABEL, "init textColor = " + textColor
                    + ", isHideArrow = " + isHideArrow
                    + ", hintText = " + hintText
                    + ", popupWindowMaxHeight = " + popupWindowMaxHeight
                    + ", paddingTop = " + paddingTop
                    + ", popupPaddingLeft = " + popupPaddingLeft
            );
        } catch (IOException e) {
            HiLog.info(LABEL, "exception = " + e.toString());
        } catch (NotExistException e) {
            HiLog.info(LABEL, "exception = " + e.toString());
        } catch (WrongTypeException e) {
            HiLog.info(LABEL, "exception = " + e.toString());
        }

        isNothingSelected = true;

        setTextAlignment(TextAlignment.VERTICAL_CENTER | TextAlignment.START);
        setClickable(true);
        setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);

        if (isRtl) {
            setLayoutDirection(LayoutDirection.RTL);
        }

        if (!isHideArrow) {
            arrowDrawable = Utils.getPixelMapElement(context, ResourceTable.Media_ic_more);
            Element[] drawables = getAroundElements();
            if (isRtl) {
                drawables[0] = arrowDrawable;
            } else {
                drawables[NUM_2] = arrowDrawable;
            }
            setAroundElements(drawables[0], drawables[1], drawables[NUM_2], drawables[NUM_3]);
        }

        if (textColor != defaultColor.getValue()) {
            setTextColor(new Color(textColor));
        }
        textSize = getTextSize();

        initPopupWindow(context);
        setTouchEventListener(this);
    }

    private void initPopupWindow(Context context) {
        listView = new ListContainer(context);
        ComponentContainer.LayoutConfig layoutConfig =
                new ComponentContainer.LayoutConfig(
                        ComponentContainer.LayoutConfig.MATCH_PARENT, ComponentContainer.LayoutConfig.MATCH_CONTENT);
        listView.setLayoutConfig(layoutConfig);
        listView.setId(getId());
        listView.setScrollbarThickness(0);
        listView.setScrollbarFadingEnabled(true);
        listView.enableScrollBar(AXIS_Y, true);
        listView.setScrollbarRoundRect(true);
        listView.setScrollbarRadius(NUM_10);
        ShapeElement background = new ShapeElement(this.getContext(), ResourceTable.Graphic_ms__drawable);
        background.setStroke(NUM_3, RgbColor.fromArgbInt(Color.getIntColor("#9496A3")));
        listView.setBackground(background);
        listView.setItemProvider(mAdapter);
        listView.setItemClickedListener((parent, view, position, id) -> {
            selectedIndex = position;
            isNothingSelected = false;
            Object item = adapter.get(position);
            adapter.notifyItemSelected(position);
            setTextColor(new Color(MaterialSpinner.this.textColor));
            collapse();
            if (onItemSelectedListener != null) {
                onItemSelectedListener.onItemSelected(MaterialSpinner.this, position, id, item);
            }
        });
        listView.setItemLongClickedListener(new ListContainer.ItemLongClickedListener() {
            @Override
            public boolean onItemLongClicked(ListContainer listContainer, Component component, int position, long id) {
                selectedIndex = position;
                isNothingSelected = false;
                Object item = adapter.get(position);
                adapter.notifyItemSelected(position);
                setTextColor(new Color(MaterialSpinner.this.textColor));
                collapse();
                if (onItemSelectedListener != null) {
                    onItemSelectedListener.onItemSelected(MaterialSpinner.this, position, id, item);
                }
                return false;
            }
        });
        popupWindow = new PopupDialog(context, null);
        popupWindow.setCustomComponent(listView);
        HiLog.info(LABEL, "backgroundColor = " + (backgroundColor != Color.WHITE.getValue()));
        if (backgroundColor != Color.WHITE.getValue()) {
            ShapeElement element = new ShapeElement();
            element.setRgbColor(RgbColor.fromArgbInt(backgroundColor));
            setBackground(element);
        }
        popupWindow.setDialogListener(() -> {
            HiLog.info(
                    LABEL, "setDialogListener isTouchOutside " + isNothingSelected + ", " + onNothingSelectedListener);
            if (popupWindow != null) {
                popupWindow.destroy();
                popupWindow = null;
            }
            if (isNothingSelected && onNothingSelectedListener != null) {
                onNothingSelectedListener.onNothingSelected(MaterialSpinner.this);
            }
            if (!isHideArrow) {
                animateArrow(false);
            }
            return true;
        });
        popupWindow.registerDisplayCallback(iDialog -> HiLog.info(LABEL, "registerDisplayCallback iDialog " + iDialog));
        popupWindow.registerRemoveCallback(new BaseDialog.RemoveCallback() {
            @Override
            public void onRemove(IDialog ifDialog) {
                HiLog.info(LABEL, "registerRemoveCallback iDialog " + ifDialog);
            }
        });
    }

    @Override
    public void setLayoutConfig(ComponentContainer.LayoutConfig config) {
        super.setLayoutConfig(config);
        HiLog.info(LABEL, "setLayoutConfig setLayoutConfig setLayoutConfig " + spinnerWidth);
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        HiLog.info(LABEL, "onTouchEvent = " + touchEvent.getAction());
        int xx = (int) touchEvent.getPointerPosition(0).getX();
        int yy = (int) touchEvent.getPointerPosition(0).getY();
        if (touchEvent.getAction() == TouchEvent.PRIMARY_POINT_UP) {
            HiLog.info(
                    LABEL, "onTouchEvent = " + "isEnabled = " + isEnabled() + ", isClickable = "
                            + isClickable() + ", popupWindow.isShowing() = " + xx + ", yy = " + yy);
            if (isEnabled() && isClickable()) {
                expand();
            }
        } else if (touchEvent.getAction() == TouchEvent.PRIMARY_POINT_DOWN) {
            return true;
        }
        return false;
    }

    /**
     * setShowFalse
     */
    public void setShowFalse() {
        isShow = false;
    }

    @Override
    public void setTextSize(int size) {
        HiLog.info(LABEL, "setTextSize adapter = " + adapter + ", textSize = " + textSize);
        if (adapter != null) {
            adapter.setTextSize(textSize);
            adapter.notifyDataChanged();
        }
        super.setTextSize(size);
    }

    @Override
    public void setTextColor(Color color) {
        HiLog.info(LABEL, "@Override setTextColor adapter = " + adapter);
        textColor = color.getValue();
        if (adapter != null) {
            adapter.setTextColor(textColor);
            adapter.notifyDataChanged();
        }
        super.setTextColor(color);
    }

    /**
     * setHintColor
     *
     * @param color
     */
    public void setHintColor(Color color) {
        hintColor = color.getValue();
        super.setTextColor(color);
    }

    @Override
    public void setEnabled(boolean enabled) {
        super.setEnabled(enabled);
    }

    /**
     * getSelectedIndex
     *
     * @return selectedIndex
     */
    public int getSelectedIndex() {
        return selectedIndex;
    }

    /**
     * setSelectedIndex
     *
     * @param position
     */
    public void setSelectedIndex(int position) {
        if (adapter != null) {
            if (position >= 0 && position <= adapter.getCount()) {
                adapter.notifyItemSelected(position);
                selectedIndex = position;
                setText(adapter.get(position).toString());
            } else {
                throw new IllegalArgumentException("Position must be lower than adapter count!");
            }
        }
    }

    public void setOnItemSelectedListener(OnItemSelectedListener onItemSelectedListener) {
        this.onItemSelectedListener = onItemSelectedListener;
    }

    public void setOnNothingSelectedListener(OnNothingSelectedListener onNothingSelectedListener) {
        this.onNothingSelectedListener = onNothingSelectedListener;
    }

    /**
     * setItems
     *
     * @param items
     * @param <T>
     */
    public <T> void setItems(T... items) {
        setItems(Arrays.asList(items));
    }

    /**
     * setItems
     *
     * @param items
     * @param <T>
     */
    public <T> void setItems(List<T> items) {
        adapter = new MaterialSpinnerAdapter<>(getContext(), items)
                .setPopupPadding(popupPaddingLeft, popupPaddingTop, popupPaddingRight, popupPaddingBottom)
                .setTextSize(textSize)
                .setTextColor(textColor);
        setAdapterInternal(adapter);
    }

    /**
     * setAdapter
     *
     * @param adapter
     * @param <T>
     */
    public <T> void setAdapter(MaterialSpinnerAdapter<T> adapter) {
        this.adapter = adapter;
        this.adapter.setTextColor(textColor);
        this.adapter.setPopupPadding(popupPaddingLeft, popupPaddingTop, popupPaddingRight, popupPaddingBottom);
        setAdapterInternal(adapter);
    }

    /**
     * setAdapterInternal
     *
     * @param baseSdapter
     */
    private void setAdapterInternal(MaterialSpinnerBaseAdapter baseSdapter) {
        this.mAdapter = baseSdapter;
        boolean isShouldResetPopupHeight = listView.getItemProvider() != null;
        baseSdapter.setHintEnabled(!Utils.isEmpty(hintText));
        listView.setItemProvider(baseSdapter);
        HiLog.info(
                LABEL, "selectedIndex = " + selectedIndex + ", shouldResetPopupHeight = " + isShouldResetPopupHeight);
        if (selectedIndex >= baseSdapter.getCount()) {
            selectedIndex = 0;
        }
        if (baseSdapter.getItems().size() > 0) {
            if (isNothingSelected && !Utils.isEmpty(hintText)) {
                setText(hintText);
                setHintColor(new Color(hintColor));
            } else {
                setTextColor(new Color(textColor));
            }
        } else {
            setText("");
        }
        if (isShouldResetPopupHeight) {
            popupWindow.setSize(getWidth(), calculatePopupWindowHeight());
        }
    }

    /**
     * getItems
     *
     * @param <T>
     * @return getItems
     */
    public <T> List<T> getItems() {
        if (adapter == null) {
            return Collections.emptyList();
        }
        return adapter.getItems();
    }

    /**
     * expand
     */
    public void expand() {
        HiLog.info(LABEL, "expand = ");
        if (!isShow) {
            if (!isHideArrow) {
                animateArrow(true);
            }
            isNothingSelected = true;
            initPopupWindow(mContext);
            popupWindow.setSize(getWidth(), calculatePopupWindowHeight());
            int[] locationOnScreen = this.getLocationOnScreen();
            HiLog.info(
                    LABEL, "locationOnScreen[1] = "
                            + locationOnScreen[1] + ", " + spinnerWidth + ",locationOnScreen[1]" + locationOnScreen[1]);
            popupWindow.showOnCertainPosition(
                    LayoutAlignment.LEFT | LayoutAlignment.TOP, locationOnScreen[0]
                            + NUM_37, locationOnScreen[1] - NUM_45);
            isShow = true;
        }
    }

    /**
     * collapse
     */
    public void collapse() {
        ComponentParent componentParent = listView.getComponentParent();
        if (componentParent != null) {
            componentParent.removeComponent(listView);
        }
        listView = null;
        popupWindow.destroy();
        popupWindow = null;
    }

    /**
     * setArrowColor
     *
     * @param color
     */
    public void setArrowColor(int color) {
        arrowColor = color;
        arrowColorDisabled = Utils.lighter(arrowColor, DEFAULT_FACTOR);
    }

    /**
     * canShowPopup
     *
     * @return canShowPopup
     */
    private boolean canShowPopup() {
        Ability activity = getActivity();
        HiLog.info(LABEL, "activity = " + activity);
        if (activity == null || activity.isTerminating()) {
            return false;
        }
        boolean isLaidOut;
        isLaidOut = getWidth() > 0 && getHeight() > 0;
        return isLaidOut;
    }

    private Ability getActivity() {
        Context context = getContext();
        HiLog.info(LABEL, "activity = "
                + context + ", activity.isTerminating() " + (context instanceof AbilityContext));
        while (context instanceof AbilityContext) {
            HiLog.info(LABEL, "activity = " + context + "(context instanceof Ability)" + (context instanceof Ability));
            if (context instanceof Ability) {
                return (Ability) context;
            }
            context = ((AbilityContext) context).getContext();
        }
        return (Ability) Collections.EMPTY_LIST;
    }

    private void animateArrow(boolean isShouldRotateUp) {
        int start = isShouldRotateUp ? 0 : NUM_10000;
        int end = isShouldRotateUp ? NUM_10000 : 0;
        if (isRtl) {
            setLayoutDirection(LayoutDirection.RTL);
        }
        arrowDrawable = Utils.getPixelMapElement(mContext, ResourceTable.Media_ic_more);
        Matrix matrix = new Matrix();
        matrix.setRotate(NUM_45);
        Element[] drawables = getAroundElements();
        if (isRtl) {
            drawables[0] = arrowDrawable;
        } else {
            drawables[NUM_2] = arrowDrawable;
        }
        setAroundElements(drawables[0], drawables[1], drawables[NUM_2], drawables[NUM_3]);
    }

    public void setDropdownMaxHeight(int height) {
        popupWindowMaxHeight = height;
    }

    public void setDropdownHeight(int height) {
        popupWindowHeight = height;
    }

    private int calculatePopupWindowHeight() {
        HiLog.info(LABEL, "calculatePopupWindowHeight adapter = " + adapter);
        if (adapter == null) {
            return ComponentContainer.LayoutConfig.MATCH_CONTENT;
        }
        float itemHeight = 0;
        try {
            itemHeight = this.getResourceManager().getElement(ResourceTable.Float_ms__item_height).getFloat();
        } catch (IOException | NotExistException | WrongTypeException e) {
            HiLog.info(LABEL, "e");
        }
        float listViewHeight = adapter.getCount() * itemHeight;
        HiLog.info(
                LABEL, "itemHeight = " + itemHeight
                        + ", listViewHeight = " + listViewHeight + ", popupWindowMaxHeight = "
                        + popupWindowMaxHeight + ", popupWindowHeight = " + popupWindowHeight);
        if (popupWindowMaxHeight > 0 && listViewHeight > popupWindowMaxHeight) {
            HiLog.info(LABEL, "if = ");
            return popupWindowMaxHeight;
        } else if (popupWindowHeight != ComponentContainer.LayoutConfig.MATCH_PARENT
                && popupWindowHeight != ComponentContainer.LayoutConfig.MATCH_CONTENT
                && popupWindowHeight <= listViewHeight) {
            HiLog.info(LABEL, "else if = ");
            return popupWindowHeight;
        } else if (listViewHeight == 0 && adapter.getItems().size() == 1) {
            HiLog.info(LABEL, "size() == 1 ");
            return (int) itemHeight;
        } else if (popupWindowHeight == ComponentContainer.LayoutConfig.MATCH_CONTENT) {
            HiLog.info(LABEL, "else MATCH_CONTENT = ");
            if (popupWindowHeight != 0) {
                return popupWindowHeight;
            } else {
                return popupWindowMaxHeight;
            }
        } else if (popupWindowHeight == ComponentContainer.LayoutConfig.MATCH_PARENT) {
            HiLog.info(LABEL, "else MATCH_PARENT = ");
            if (popupWindowHeight > 0) {
                return popupWindowHeight;
            } else {
                return popupWindowMaxHeight;
            }
        }
        return ComponentContainer.LayoutConfig.MATCH_CONTENT;
    }

    public PopupDialog getPopupWindow() {
        return popupWindow;
    }

    public ListContainer getListView() {
        return listView;
    }

    /**
     * OnItemSelectedListener
     *
     * @param <T>
     * @since 2021-03-27
     */
    public interface OnItemSelectedListener<T> {
        /**
         * onItemSelected
         *
         * @param view
         * @param position
         * @param id
         * @param item
         */
        void onItemSelected(MaterialSpinner view, int position, long id, T item);
    }

    /**
     * OnNothingSelectedListener
     *
     * @since 2021-03-27
     */
    public interface OnNothingSelectedListener {
        /**
         * onNothingSelected
         *
         * @param spinner
         */
        void onNothingSelected(MaterialSpinner spinner);
    }
}